﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using ArivisBuildService.Properties;
using BuildServer.Models;
using System.Threading;

namespace ArivisBuildService
{
    class BuildServiceWorkerThread
    {
        private Thread m_thread;
        private bool m_running;
        private object m_wait = new object();

        /// <summary>
        /// Starts the build service worker thread
        /// </summary>
        public void Start()
        {
            Stop();
            m_running = true;
            m_thread = new Thread(this.ThreadRun);
            m_thread.Start();
        }

        /// <summary>
        /// Stops the build service worker thread
        /// </summary>
        public void Stop()
        {
            m_running = false;
            this.Notify();
            if (m_thread != null)
                m_thread.Join();
            
            m_thread = null;
        }

        /// <summary>
        /// Notifies the build service worker thread to continue working
        /// </summary>
        public void Notify()
        {
            lock (m_wait)
            {
                Monitor.Pulse(m_wait);
            }
        }

        /// <summary>
        /// Schedules all nightly builds
        /// </summary>
        public void Nightly()
        {
            int[] nighltyIDs = new int[0];
            using (var db = new BuildServerDbContext())
            {
                var nightlyBuildDefinitions = db.BuildDefinitions.Where(x => x.IsNightly).ToList();
                foreach (var buildDefinition in nightlyBuildDefinitions)
                {
                    // find build run
                    var buildRun = db.BuildRuns.FirstOrDefault(x => x.BuildDefinitionId == buildDefinition.Id);
                    // create new build run if required
                    if (buildRun == null)
                    {
                        buildRun = new BuildRun();
                        buildRun.BuildDefinitionId = buildDefinition.Id;
                        db.BuildRuns.Add(buildRun);
                    }
                    // schedule build run
                    buildRun.Targets = Settings.Default.NighltyTarget;
                    buildRun.DoSchedule();
                    Console.WriteLine("{0}: Scheduled {1}", DateTime.Now, buildDefinition.Name);
                }
                db.SaveChanges();
            }

            // run scheduled builds
            Notify();
        }

        private void ThreadRun()
        {
            while(m_running)
            {
                // execute all scheduled builds
                var runInfo = BuildRunInfo.GetNext();
                while (runInfo != null)
                {
                    ExecuteBuildRun(runInfo);
                    runInfo = BuildRunInfo.GetNext();
                }

                // go to slepp until there is more to do
                lock (m_wait)
                {
                    Monitor.Wait(m_wait);
                }
            }
        }

        /// <summary>
        /// Execute the next scheduled build run
        /// </summary>
        private void ExecuteBuildRun(BuildRunInfo runInfo)
        {
            // sanity check
            if (runInfo == null)
                return;

            // create build files
            File.WriteAllText(runInfo.BuildPropsPath, runInfo.BuildProps, System.Text.Encoding.UTF8);
            File.WriteAllText(runInfo.BuildScriptPath, runInfo.BuildScript, System.Text.Encoding.UTF8);

            // prepare MSBuild
            var startInfo = new System.Diagnostics.ProcessStartInfo();
            startInfo.FileName = Settings.Default.MSBuildPath;
            startInfo.WorkingDirectory = runInfo.WorkingDirectory;
            startInfo.Arguments = string.Format("{0} /t:{1} /fl /flp:logfile={2}", runInfo.BuildScriptPath, runInfo.Targets, runInfo.BuildLogPath);
            startInfo.UseShellExecute = true;

            // run MSBuild
            try
            {
                Console.WriteLine("{0}: Started build {1} - {2}", System.DateTime.Now, runInfo.Label, runInfo.Targets);
                var process = Process.Start(startInfo);
                process.WaitForExit();
                FinishBuild(runInfo);
                Console.WriteLine("{0}: Finished build {1}", System.DateTime.Now, runInfo.Label);
            }
            catch (Exception ex)
            {
                Console.WriteLine("{0}: build {1} failed: {2}", System.DateTime.Now, runInfo.Label, ex);
            }
        }

        /// <summary>
        /// Update BuildRun after msbuild has finished
        /// </summary>
        /// <param name="buildRunId"></param>
        /// <param name="buildRunInfo"></param>
        private void FinishBuild(BuildRunInfo buildRunInfo)
        {
            using (var db = new BuildServerDbContext())
            {
                // find build run
                var dbset = db.BuildRuns;
                var buildRun = dbset.FirstOrDefault(x => x.Id == buildRunInfo.BuildRunId);
                if (buildRun == null)
                    return;

                // find compile log
                string compileLog = buildRunInfo.CompileLogPath;
                if (!File.Exists(compileLog))
                {
                    Console.WriteLine("{0}: compilelog.html not found: {1}", System.DateTime.Now, compileLog);
                    compileLog = null;
                }

                // update build run
                buildRun.End = DateTime.Now;
                buildRun.BuildLog = buildRunInfo.BuildLogPath;
                buildRun.CompileLog = compileLog;

                db.SaveChanges();
            }
        }
    }
}
